﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using Common.Utilities.TextMatch;
using PdfLib;

namespace Workflows.Components.TextMining.DocumentLabels
{
    /// <summary>
    /// 
    /// </summary>
    public class DocumentLabelWorker:IWorker 
    {
        #region Implementation of IWorker

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        private Thread workerThread;
        private DocumentLabelInstruction instruction;
        private DocumentLabelOutput output;
        private RunContext _Context;
        private Guid _TicketID;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        /// <summary></summary>
        public Thread WorkerThread
        {
            get { return workerThread; }
        }
        /// <summary></summary>
        public InstructionBase Instruction
        {
            get { return instruction; }
        }
        /// <summary></summary>
        public ResultBase Output
        {
            get { return output; }
        }
        /// <summary></summary>
        public Type ComponentType
        {
            get { return typeof(DocumentLabelComponent); }
        }
        /// <summary></summary>
        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }
        /// <summary></summary>
        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (DocumentLabelInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }
        /// <summary></summary>
        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (DocumentLabelInstruction)request;
            this._Context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        /// <summary></summary>
        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = Thread.CurrentThread;
            this.LabelDocuments();
        }

        /// <summary></summary>
        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = new Thread(new ThreadStart(this.LabelDocuments));
            this.workerThread.Start();
        }

        /// <summary></summary>
        public void CancelWork()
        {
            if (this.workerThread != null && this.workerThread.IsAlive)
            {
                this.workerThread.Abort();
                this.workerThread.Join();

                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._TicketID, this.GetType(), this.instruction);
                }
            }
        }

        #endregion

        #region work
        private void LabelDocuments()
        {

        }

        #endregion

        #region freqs and ranges
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdfFilePath"></param>
        /// <returns></returns>
        public static string GetPdfFileContent(string pdfFilePath)
        {
            return PdfTextReader.ReadAll(pdfFilePath);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="docContent"></param>
        /// <returns></returns>
        public static Dictionary<string, int> GetWordFrequencies(string docContent)
        {
            Dictionary<string, int> wordFreqs = new Dictionary<string, int>();
            if (!string.IsNullOrEmpty(docContent))
            {
                string[] words =
                    docContent.Split(
                        new char[]
                       {
                           ' ', '.', ',', '\n', '\r', '-', '(', ')', '{', '}',
                           '[', ']', ':', ';', '"', '\'', '!', '`', '~',
                           '@', '#', '$', '%', '^', '&', '*', '=', '+', '?',
                           '/', '\\', '<', '>'
                       });
                if (words != null && words.Length > 0)
                {
                    foreach (string word in words)
                    {
                        if (word.Length > 1)
                        {
                            if (wordFreqs.ContainsKey(word.ToLower()))
                                wordFreqs[word.ToLower()] = wordFreqs[word.ToLower()] + 1;
                            else
                                wordFreqs.Add(word.ToLower(), 1);
                        }
                    }
                }
            }
            return wordFreqs;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="docContent"></param>
        /// <returns></returns>
        public static Dictionary<string, List<int>> GetWordPositions(string docContent)
        {
            Dictionary<string, List<int>> wordPositions=new Dictionary<string, List<int>>();
            if (!string.IsNullOrEmpty(docContent))
            {
                string[] words =
                    docContent.Split(
                        new char[]
                       {
                           ' ', '.', ',', '\n', '\r', '-', '(', ')', '{', '}',
                           '[', ']', ':', ';', '"', '\'', '!', '`', '~',
                           '@', '#', '$', '%', '^', '&', '*', '=', '+', '?',
                           '/', '\\', '<', '>'
                       });
                
                if (words != null && words.Length > 0)
                {
                    int position = 0;
                    foreach (string word in words)
                    {
                        if (word.Length > 1)
                        {
                            position++;
                            if (wordPositions.ContainsKey(word.ToLower()))
                            {
                                List<int> positions = wordPositions[word.ToLower()];
                                positions.Add(position);
                                wordPositions[word.ToLower()] = positions;
                            }
                            else
                            {
                                List<int> positions = new List<int>();
                                positions.Add(position);
                                wordPositions.Add(word.ToLower(), positions);
                            }
                        }
                    }
                }
            }

            return wordPositions;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="docContent"></param>
        /// <returns></returns>
        public static Dictionary<string, List<Range<int>>> GetWordRanges(string docContent)
        {
            Dictionary<string, List<Range<int>>> wordRanges=
                new Dictionary<string, List<Range<int>>>();
            List<char> wordSplitters = new List<char>();
            char[] splitters = new char[]{
                           ' ', '.', ',', '\n', '\r', '-', '(', ')', '{', '}',
                           '[', ']', ':', ';', '"', '\'', '!', '`', '~',
                           '@', '#', '$', '%', '^', '&', '*', '=', '+', '?',
                           '/', '\\', '<', '>'
                       };
            wordSplitters.AddRange(splitters);
            if(!string.IsNullOrEmpty(docContent))
            {
                int charPos = -1;
                string attachedWord = string.Empty;
                int wordStart = 0;
                foreach (char c in docContent)
                {
                    charPos++;
                    if(splitters.Contains(c))
                    {
                        if(!string.IsNullOrEmpty(attachedWord) && charPos > wordStart)
                        {
                            Range<int> range = new Range<int>(wordStart, charPos);
                            if (wordRanges.ContainsKey(attachedWord.ToLower()))
                            {
                                List<Range<int>> ranges = wordRanges[attachedWord.ToLower()];
                                ranges.Add(range);
                                wordRanges[attachedWord.ToLower()] = ranges;
                            }
                            else
                            {
                                List<Range<int>> ranges = new List<Range<int>>();
                                ranges.Add(range);
                                wordRanges.Add(attachedWord.ToLower(), ranges);
                            }
                        }
                        attachedWord = string.Empty;
                        wordStart = charPos + 1;
                    }
                    else
                    {
                        attachedWord += c;
                    }
                }
                if (wordStart < docContent.Length - 1 && !string.IsNullOrEmpty(attachedWord))
                {
                    charPos = docContent.Length - 1;
                    Range<int> range = new Range<int>(wordStart, charPos);
                    if (wordRanges.ContainsKey(attachedWord.ToLower()))
                    {
                        List<Range<int>> ranges = wordRanges[attachedWord.ToLower()];
                        ranges.Add(range);
                        wordRanges[attachedWord.ToLower()] = ranges;
                    }
                    else
                    {
                        List<Range<int>> ranges=new List<Range<int>>();
                        ranges.Add(range);
                        wordRanges.Add(attachedWord.ToLower(), ranges);
                    }
                }
            }
            return wordRanges;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="docContent"></param>
        /// <returns></returns>
        public static Dictionary<int,Range<int>> GetSetenceRanges(string docContent)
        {
            Dictionary<int, Range<int>> setences = new Dictionary<int, Range<int>>();
            if (!string.IsNullOrEmpty(docContent))
            {
                int setenceID = 0;
                int charPos = -1;
                int setenceStart = 0;
                string attachedWord = string.Empty;
                foreach (char c in docContent)
                {
                    charPos++;
                    if(c=='?' || c=='!')
                    {
                        if(charPos > setenceStart)
                        {
                            Range<int> setenceRange=new Range<int>(setenceStart, charPos);
                            setenceID++;
                            setences.Add(setenceID,setenceRange);
                            setenceStart = charPos + 1;
                            attachedWord = string.Empty;
                        }
                    }
                    else if ((c == '"' || c == '\'') && (docContent.Length > charPos + 1 && docContent[charPos + 1] == '.'))
                    {
                        if (charPos > setenceStart)
                        {
                            Range<int> setenceRange = new Range<int>(setenceStart, charPos);
                            setenceID++;
                            setences.Add(setenceID, setenceRange);
                            setenceStart = charPos + 1;
                            attachedWord = string.Empty;
                        }
                    }
                    //else if (c == ')' || c == ']' || c == '}')
                    //{
                    //    if (charPos > setenceStart)
                    //    {
                    //        Range<int> setenceRange = new Range<int>(setenceStart, charPos);
                    //        setenceID++;
                    //        setences.Add(setenceID, setenceRange);
                    //        setenceStart = charPos + 1;
                    //        attachedWord = string.Empty;
                    //    }
                    //}
                    else if (docContent.Length > charPos + 1 && 
                        (docContent[charPos + 1] == '"' ||
                        docContent[charPos + 1] == '\''))
                    {
                        if (charPos > setenceStart)
                        {
                            Range<int> setenceRange = new Range<int>(setenceStart, charPos);
                            setenceID++;
                            setences.Add(setenceID, setenceRange);
                            setenceStart = charPos + 1;
                            attachedWord = string.Empty;
                        }
                    }
                    else if (c==' ' || c=='\n' || c=='\r')
                    {
                        attachedWord = string.Empty;
                    }
                    else if (c == '\r' && docContent.Length > charPos + 1 &&
                        docContent[charPos + 1] == '\n')
                    {
                        if (charPos > setenceStart)
                        {
                            Range<int> setenceRange = new Range<int>(setenceStart, charPos);
                            setenceID++;
                            setences.Add(setenceID, setenceRange);
                            setenceStart = charPos + 1;
                            attachedWord = string.Empty;
                        }
                    }
                    else if (c == '.')
                    {
                        if (!string.IsNullOrEmpty(attachedWord) && attachedWord.IndexOf('.') >= 0)
                        {
                            attachedWord += c;
                        }
                        else if (docContent.Length > charPos + 1 && docContent[charPos + 1] == '.')
                        {
                            attachedWord += c;
                        }
                        else if (!string.IsNullOrEmpty(attachedWord) &&
                            Char.IsLower(attachedWord[0]) &&
                            docContent.Length > charPos + 1 &&
                            (docContent[charPos + 1] == ' ' || Char.IsUpper(docContent[charPos + 1])))
                        {
                            if (charPos > setenceStart)
                            {
                                Range<int> setenceRange = new Range<int>(setenceStart, charPos);
                                setenceID++;
                                setences.Add(setenceID, setenceRange);
                                setenceStart = charPos + 1;
                                attachedWord = string.Empty;
                            }
                        }
                        else if (string.IsNullOrEmpty(attachedWord) || attachedWord.Length < 2)
                        {
                            attachedWord += c;
                        }
                        else if (!string.IsNullOrEmpty(attachedWord) &&
                            attachedWord.Length < 5 &&
                            docContent.Length > charPos + 1 &&
                            Char.IsUpper(docContent[charPos + 1]))
                        {
                            attachedWord += c;
                        }
                        else
                        {
                            if (charPos > setenceStart)
                            {
                                Range<int> setenceRange = new Range<int>(setenceStart, charPos);
                                setenceID++;
                                setences.Add(setenceID, setenceRange);
                                setenceStart = charPos + 1;
                                attachedWord = string.Empty;
                            }
                        }
                    }
                    else
                        attachedWord += c;
                }
                if (setenceStart < docContent.Length - 1)
                {
                    charPos = docContent.Length - 1;
                    Range<int> setenceRange = new Range<int>(setenceStart, charPos);
                    setenceID++;
                    setences.Add(setenceID, setenceRange);
                }
            }
            return setences;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="docContent"></param>
        /// <returns></returns>
        public static Dictionary<int,Range<int>> GetParagraphRanges(string docContent)
        {
            Dictionary<int,Range<int>> paragraphRanges=new Dictionary<int, Range<int>>();
            if (!string.IsNullOrEmpty(docContent))
            {
                int paragraphID = 0;
                int paragraphStart = 0;
                int charPos = docContent.IndexOf("\r\n", paragraphStart);
                while(charPos>=0)
                {
                    paragraphID += 1;
                    Range<int> range = new Range<int>(paragraphStart, charPos);
                    paragraphRanges.Add(paragraphID, range);
                    paragraphStart = charPos + 2;
                    charPos = docContent.IndexOf("\r\n", paragraphStart);
                }

                if (paragraphStart < docContent.Length - 1)
                {
                    charPos = docContent.Length - 1;
                    Range<int> range = new Range<int>(paragraphStart, charPos);
                    paragraphID++;
                    paragraphRanges.Add(paragraphID, range);
                }
            }
            return paragraphRanges;
        }
        #endregion

        #region co-occurance
        /// <summary>
        /// 
        /// </summary>
        /// <param name="wordFreqs"></param>
        /// <param name="wordRanges"></param>
        /// <param name="setenceRanges"></param>
        /// <param name="keySetenceIDs"></param>
        /// <returns></returns>
        public static Dictionary<string, Dictionary<string, double>> CalculateWordInteractions(
            Dictionary<string, int> wordFreqs,
            Dictionary<string, List<Range<int>>> wordRanges,
            Dictionary<int, Range<int>> setenceRanges)
        {
            StopWordsUtil stopWordsUtil = new StopWordsUtil(StopWordsUtil.StopWordList.PubmedStopWordList);
            List<string> stopWordList = stopWordsUtil.StopWords;
            int totalWordCount = 0;
            foreach (string word in wordFreqs.Keys)
                totalWordCount += wordFreqs[word];
            Dictionary<string, Dictionary<string, double>> wordIneractions = new Dictionary<string, Dictionary<string, double>>();
            string[] words = new string[wordFreqs.Count];
            wordFreqs.Keys.CopyTo(words, 0);
            for (int i = 0; i < words.Length; i++)
            {
                string word1 = words[i];
                if (stopWordList.Contains(word1) || word1.Length < 2)
                    continue;
                Dictionary<string, double> toWordInteractions = new Dictionary<string, double>();
                double word1FreqInSetence = (double)wordFreqs[word1] / totalWordCount * setenceRanges.Count;
                List<Range<int>> word1Ranges = wordRanges[word1];
                List<int> setenceIDs1 = new List<int>();
                foreach (int setenceID in setenceRanges.Keys)
                {
                    Range<int> setenceRange = setenceRanges[setenceID];
                    bool containsWord1 = false;
                    foreach (Range<int> word1Range in word1Ranges)
                    {
                        if (setenceRange.Contains(word1Range) || setenceRange.OverlapWith(word1Range))
                        {
                            containsWord1 = true;
                            break;
                        }
                    }
                    if (containsWord1)
                        setenceIDs1.Add(setenceID);
                }
                for (int k = i + 1; k < words.Length; k++)
                {
                    string word2 = words[k];
                    if (stopWordList.Contains(word2) || word2.Length < 2)
                        continue;
                    double word2FreqInSetence = (double)wordFreqs[word2] / totalWordCount * setenceRanges.Count;
                    List<Range<int>> word2Ranges = wordRanges[word2];
                    List<int> setenceIDs2 = new List<int>();
                    foreach (int setenceID in setenceRanges.Keys)
                    {
                        bool containsWord2 = false;
                        Range<int> setenceRange = setenceRanges[setenceID];
                        foreach (Range<int> word2Range in word2Ranges)
                        {
                            if (setenceRange.Contains(word2Range) || setenceRange.OverlapWith(word2Range))
                            {
                                containsWord2 = true;
                                break;
                            }
                        }
                        if (containsWord2)
                            setenceIDs2.Add(setenceID);
                    }
                    List<int> sharedSetenceIDs = IntUtil.Intersection(setenceIDs1, setenceIDs2);
                    if (sharedSetenceIDs.Count > 0)
                    {
                        double expectedCoOccuranceInSetence = word1FreqInSetence * word2FreqInSetence;
                        if (sharedSetenceIDs.Count > expectedCoOccuranceInSetence)
                        {
                            toWordInteractions.Add(word2, sharedSetenceIDs.Count / expectedCoOccuranceInSetence);
                        }
                    }
                }
                wordIneractions.Add(word1, toWordInteractions);
            }
            return wordIneractions;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="keywords"></param>
        /// <param name="wordRanges"></param>
        /// <param name="setenceRanges"></param>
        /// <param name="wordThreshold"></param>
        /// <returns></returns>
        public static Dictionary<int,double> GetKeySetences(
            Dictionary<string,int> keywords,
            Dictionary<string,List<Range<int>>> wordRanges,
            Dictionary<int,Range<int>> setenceRanges,
            double wordThreshold, int minCharacterCountPerSetence)
        {
            List<string> filteredWords = new List<string>();
            foreach(string keyword in keywords.Keys)
            {
                if(keywords[keyword]>=wordThreshold)
                {
                    filteredWords.Add(keyword);
                }
            }
            Dictionary<int,double> setenceScores=new Dictionary<int, double>();
            foreach(string filteredWord in filteredWords)
            {
                List<Range<int>> ranges = wordRanges[filteredWord];
                foreach(int setenceID in setenceRanges.Keys)
                {
                    bool containsWord = false;
                    Range<int> setenceRange = setenceRanges[setenceID];
                    foreach(Range<int> range in ranges)
                    {
                        if(setenceRange.Contains(range) || setenceRange.OverlapWith(range))
                        {
                            containsWord = true;
                            break;
                        }
                    }
                    if(containsWord)
                    {
                        int setenceLength = Math.Max(minCharacterCountPerSetence,
                                                     setenceRange.ToValue - setenceRange.FromValue + 1);
                        double setenceScore = (double)keywords[filteredWord] / setenceLength;
                        if (setenceScores.ContainsKey(setenceID))
                            setenceScores[setenceID] = setenceScores[setenceID] + setenceScore;
                        else
                            setenceScores.Add(setenceID, setenceScore);
                    }
                }
            }
            return setenceScores;
        }
        #endregion
    }
}
